package zom.zenjava.zendeploy.gui.jfx;

import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.geometry.Pos;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.ProgressBar;
import javafx.scene.control.ProgressIndicator;
import javafx.scene.layout.*;
import zom.zenjava.zendeploy.ZenDeployException;
import zom.zenjava.zendeploy.ZenDeployManager;
import zom.zenjava.zendeploy.model.ApplicationProfile;
import zom.zenjava.zendeploy.model.CheckForUpdateResult;

import java.util.logging.Level;
import java.util.logging.Logger;

public class ApplicationUpdateWizard
{
    private final static Logger LOGGER = Logger.getLogger(ApplicationUpdateWizard.class.getName());

    private StackPane container;
    private BorderPane glasspane;
    private BorderPane root;
    private Label titleLabel;
    private Button closeButton;
    private BorderPane contentArea;

    private Node checkingForUpdatesArea;
    private CheckForUpdateService checkForUpdateService;

    private Node upgradeAvailableArea;
    private ApplicationProfileView upgradeProfileView;
    private Button upgradeButton;

    private Node upgradingArea;
    private ProgressBar upgradeProgressBar;
    private Label upgradingProgressLabel;
    private UpgradeToLatestVersionService upgradeToLatestVersionService;

    private Node upToDateArea;

    private Node upgradeCompleteArea;

    private Node errorArea;
    private Label errorMessageLabel;

    public ApplicationUpdateWizard(StackPane container)
    {
        this.container = container;
        buildView();
    }

    public void show()
    {
        if (glasspane == null)
        {
            glasspane = new BorderPane();
            glasspane.getStyleClass().add("application-update-glasspane");
            root.setMaxSize(400, 300);
            glasspane.setCenter(root);
        }
        checkForUpdates();
        container.getChildren().add(glasspane);
    }

    public void hide()
    {
        container.getChildren().remove(glasspane);
    }

    protected void checkForUpdates()
    {
        checkForUpdateService.restart();
        contentArea.setCenter(checkingForUpdatesArea);
    }

    protected void showUpgradeAvailable(ApplicationProfile appProfile)
    {
        upgradeProfileView.setApplicationProfile(appProfile);
        contentArea.setCenter(upgradeAvailableArea);
    }

    protected void showUpToDate(ApplicationProfile appProfile)
    {
        contentArea.setCenter(upToDateArea);
    }

    protected void startUpgrade()
    {
        upgradeToLatestVersionService.restart();
        contentArea.setCenter(upgradingArea);
    }

    protected void showError(String message, Throwable e)
    {
        LOGGER.log(Level.SEVERE, message, e);
        errorMessageLabel.setText(message);
        contentArea.setCenter(errorArea);
    }

    protected void showUpgradeComplete(boolean restartNeeded)
    {
        // todo optional restart - or should it always just assume restart?
        contentArea.setCenter(upgradeCompleteArea);
    }

    private void buildView()
    {
        root = new BorderPane();
        root.getStyleClass().add("application-update-wizard");
        root.setTop(buildTitleArea());
        root.setCenter(buildContentArea());
    }

    private Node buildTitleArea()
    {
        BorderPane pane = new BorderPane();
        pane.getStyleClass().add("title-bar");

        titleLabel = new Label("Update Application");
        pane.setLeft(titleLabel);

        closeButton = new Button("X");
        closeButton.setOnAction(new EventHandler<ActionEvent>()
        {
            public void handle(ActionEvent event)
            {
                hide();
            }
        });
        pane.setRight(closeButton);

        return pane;
    }

    private Node buildContentArea()
    {
        contentArea = new BorderPane();
        contentArea.getStyleClass().add("content-area");

        checkingForUpdatesArea = buildCheckingForUpdatesArea();
        upgradeAvailableArea = buildUpgradeAvailableArea();
        upToDateArea = buildUpToDateArea();
        upgradingArea = buildUpgradingArea();
        upgradeCompleteArea = buildUpgradeCompleteArea();
        errorArea = buildErrorArea();

        return contentArea;
    }

    private Node buildCheckingForUpdatesArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        ProgressIndicator progressIndicator = new ProgressIndicator();
        VBox.setVgrow(progressIndicator, Priority.NEVER);
        box.getChildren().add(progressIndicator);

        Label progressLabel = new Label("Checking for updates...");
        VBox.setVgrow(progressLabel, Priority.NEVER);
        box.getChildren().add(progressLabel);

        checkForUpdateService = new CheckForUpdateService();
        progressIndicator.progressProperty().bind(checkForUpdateService.progressProperty());
        checkForUpdateService.setOnSucceeded(new EventHandler<WorkerStateEvent>()
        {
            public void handle(WorkerStateEvent event)
            {
                CheckForUpdateResult result = checkForUpdateService.getValue();
                if (result.getUpdatedApplicationProfile() != null)
                {
                    showUpgradeAvailable(result.getUpdatedApplicationProfile());
                }
                else
                {
                    showUpToDate(result.getCurrentApplicationProfile());
                }
            }
        });
        checkForUpdateService.setOnFailed(new EventHandler<WorkerStateEvent>()
        {
            public void handle(WorkerStateEvent event)
            {
                showError("Error checking for updates", checkForUpdateService.getException());
            }
        });

        return box;
    }

    private Node buildUpgradeAvailableArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        Label label = new Label("A new version is available for download");
        box.getChildren().add(label);

        upgradeProfileView = new ApplicationProfileView();
        box.getChildren().add(upgradeProfileView);

        upgradeButton = new Button("Upgrade now");
        upgradeButton.setOnAction(new EventHandler<ActionEvent>()
        {
            public void handle(ActionEvent event)
            {
                startUpgrade();
            }
        });
        box.getChildren().add(upgradeButton);

        return box;
    }

    private Node buildUpToDateArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        Label label = new Label("You have the latest version installed");
        box.getChildren().add(label);

        return box;
    }

    private Node buildUpgradingArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        upgradeProgressBar = new ProgressBar();
        VBox.setVgrow(upgradeProgressBar, Priority.NEVER);
        box.getChildren().add(upgradeProgressBar);

        upgradingProgressLabel = new Label("Upgrading...");
        VBox.setVgrow(upgradingProgressLabel, Priority.NEVER);
        box.getChildren().add(upgradingProgressLabel);

        upgradeToLatestVersionService = new UpgradeToLatestVersionService();
        upgradeProgressBar.progressProperty().bind(checkForUpdateService.progressProperty());
        upgradeToLatestVersionService.setOnSucceeded(new EventHandler<WorkerStateEvent>()
        {
            public void handle(WorkerStateEvent event)
            {
                showUpgradeComplete(upgradeToLatestVersionService.getValue());
            }
        });
        upgradeToLatestVersionService.setOnFailed(new EventHandler<WorkerStateEvent>()
        {
            public void handle(WorkerStateEvent event)
            {
                showError("Error upgrading to latest version", upgradeToLatestVersionService.getException());
            }
        });

        return box;
    }

    private Node buildUpgradeCompleteArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        Label label = new Label("Upgrade complete! You will need to restart");
        VBox.setVgrow(label, Priority.NEVER);
        box.getChildren().add(label);

        Button restartButton = new Button("Restart");
        VBox.setVgrow(restartButton, Priority.NEVER);
        restartButton.setOnAction(new EventHandler<ActionEvent>()
        {
            public void handle(ActionEvent event)
            {
                try
                {
                    ZenDeployManager.getInstance().restart();
                }
                catch (ZenDeployException e)
                {
                    showError("Unable to restart, please restart manually", e);
                }
            }
        });
        box.getChildren().add(restartButton);

        return box;
    }

    private Node buildErrorArea()
    {
        VBox box = new VBox(10);
        box.setAlignment(Pos.CENTER);
        box.setFillWidth(false);

        errorMessageLabel = new Label("Sorry, something went wrong");
        box.getChildren().add(errorMessageLabel);

        closeButton = new Button("Ok");
        closeButton.setOnAction(new EventHandler<ActionEvent>()
        {
            public void handle(ActionEvent event)
            {
                hide();
            }
        });
        box.getChildren().add(closeButton);

        return box;
    }
}
